﻿using System;
using System.Linq;
using System.Linq.Expressions;
using Affinity.Synthesis.ExpressionCells.Metadata;
using Affinity.Core.Utility;

namespace Affinity.Synthesis.ExpressionCells.Basis
{
    /// <summary>
    /// Represents a lambda expression (an expression that was already a lambda expression before "atomization").
    /// </summary>
    internal class LambdaExpressionCell : ExpressionCellBase
    {
        /// <summary>
        /// Constructor accepting the LambdaExpression to wrap, along with the parameters that are used in that LambdaExpression
        /// but are not listed in its parameter collection.
        /// </summary>
        /// <param name="lambdaExpression"></param>
        /// <param name="outerParameters"></param>
        public LambdaExpressionCell(LambdaExpression lambdaExpression, params ParameterExpression[] outerParameters)
            : base(lambdaExpression)
        {
            if (lambdaExpression == null) throw new ArgumentNullException("lambdaExpression");
            _LambdaExpression = lambdaExpression;
            Metadata = Expressions.GetAllPropertyReferences(lambdaExpression.Body).Select(property => new PropertyDependency(property)).ToArray();

            _CompiledDelegate = Expressions.CompileSync(Expression.Lambda(lambdaExpression.Body, lambdaExpression.Parameters.Union(outerParameters).ToArray()));
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="existing"></param>
        protected LambdaExpressionCell(LambdaExpressionCell existing)
            : base(existing)
        {
            _CompiledDelegate = existing._CompiledDelegate;
            _LambdaExpression = existing._LambdaExpression;
        }

        /// <summary>
        /// Creates and returns a copy of this expression cell.
        /// </summary>
        /// <returns></returns>
        public override IExpressionCell Copy()
        {
            return new LambdaExpressionCell(this);
        }

        /// <summary>
        /// Returns a description of this expression cell.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("{0}: {1}", GetType().Name, _LambdaExpression);
        }

        /// <summary>
        /// "Calculates" the delegate-typed output of this expression cell by closing over the inner parameters (provided via this method's "inputs" parameter) and creating a delegate
        /// accepting only the outer parameters (which will later be provided via arguments passed when the delegate is invoked).
        /// </summary>
        /// <param name="inputs"></param>
        /// <param name="hasValue"></param>
        /// <param name="value"></param>
        protected override void CalculateOutput(object[] inputs, out bool hasValue, out object value)
        {
            hasValue = true;

            var methodClosure = MethodClosure.Create(_CompiledDelegate, Description, inputs, 
                _LambdaExpression.Parameters.Select(parameter => parameter.Type).Concat(new Type[] { _LambdaExpression.ReturnType }).ToArray());

            value = methodClosure.OuterDelegate;
        }

        private Delegate _CompiledDelegate = null;
        private LambdaExpression _LambdaExpression = null;
    }
}
